#include "sekai/EventList.h"
#include "sekai/VoiceSampler.h"
#include "sekai/midi.h"
#include <math.h>
#include <sndfile.h>
#include <stdio.h>

#define ZERO_MEMORY(x) memset(&x, 0, sizeof(x));

struct PhoEvent {
  struct PhoEvent *next;
  float start;
  float end;
  unsigned int index;
  unsigned char points;
  char flags[3];
  float x[256];
  float y[256];
};

struct PitchEvent {
  struct PitchEvent *next;
  float start;
  float end;
  float notenum;
  int flags;
};

class MyVoiceSampler : public VoiceSampler {

  virtual bool addOnePulse();

private:
  int _samplerate = 0;
  int _mbrPeriod = 100;
  float *_impulseResponse;
  EventList<PhoEvent> _phoEvents;
  EventList<PitchEvent> _pitchEvents;
  std::vector<VoiceRecord *> _voices;
#define IMPULSE_RESPONSE_MAX 1024

  void getImpulseResponse(float pos, PhoEvent *event, float *impulseResponse,
                          int *impulseResponseLength, float morph);

public:
  MyVoiceSampler(int buffersize, std::string voicepath)
      : VoiceSampler(buffersize) {
    _impulseResponse = new float[IMPULSE_RESPONSE_MAX];
    // printf("impulse_response\n");

    for (int i = 0; i < 3; i++) {
      VoiceRecord *voice = new VoiceRecord();
      if (i == 0)
        VoiceSampler::load(*voice, _samplerate, voicepath + "/resynth_do.wav");
      if (i == 1)
        VoiceSampler::load(*voice, _samplerate, voicepath + "/resynth_re.wav");
      if (i == 2)
        VoiceSampler::load(*voice, _samplerate, voicepath + "/resynth_mi.wav");
      _voices.push_back(voice);
    }

    {
      // TODO read oto ini since we are using an UTAU voicebank
      PhoEvent *e = new PhoEvent;
      e->start = 0.0f;
      e->end = 1.0f;
      e->index = 0;
      e->points = 3;
      e->x[0] = e->start;
      e->x[1] = e->start + 0.1f;
      e->x[2] = e->end;
      e->y[0] = 0.0f;
      e->y[1] = 0.3f;
      e->y[2] = 3.0f;
      _phoEvents.addEvent(e);
      e = new PhoEvent;
      e->start = 0.95f;
      e->end = 2.0f;
      e->index = 1;
      e->points = 3;
      e->x[0] = e->start;
      e->x[1] = e->start + 0.1f;
      e->x[2] = e->end;
      e->y[0] = 0.0f;
      e->y[1] = 0.3f;
      e->y[2] = 3.0f;
      _phoEvents.addEvent(e);
      e = new PhoEvent;
      e->start = 1.95f;
      e->end = 3.0f;
      e->index = 2;
      e->points = 3;
      e->x[0] = e->start;
      e->x[1] = e->start + 0.1f;
      e->x[2] = e->end;
      e->y[0] = 0.0f;
      e->y[1] = 0.3f;
      e->y[2] = 3.0f;
      _phoEvents.addEvent(e);
    }
    {
      float shift = 12.0f;
      PitchEvent *e = new PitchEvent;
      e->start = 0.0f;
      e->end = 1.0f;
      e->notenum = 60 + shift;
      _pitchEvents.addEvent(e);
      e = new PitchEvent;
      e->start = 1.0f;
      e->end = 2.0f;
      e->notenum = 62 + shift;
      _pitchEvents.addEvent(e);
      e = new PitchEvent;
      e->start = 2.0f;
      e->end = 3.0f;
      e->notenum = 64 + shift;
      _pitchEvents.addEvent(e);
    }

    _samplerate = 44100;
  }
  int samplerate() { return _samplerate; }
};

void MyVoiceSampler::getImpulseResponse(float currentTime, PhoEvent *event,
                                        float *impulseResponse,
                                        int *impulseResponseLength,
                                        float morph) {

  // pitchMarks for OLA
  Pitch pmkL, pmkR;
  Pitch f0L, f0R;

  float f0_interp = 0;
  float pmk_interp = 0;

  auto voice = _voices[event->index];

  float localTime = interp_linear(event->x, event->y,
                                  static_cast<int>(event->points), currentTime);

  int pmkIndex =
      findPitchMark(voice->pmkTrack, localTime, pmkL, pmkR, &pmk_interp);
  findPitchMark(voice->f0Track, currentTime, f0L, f0R, &f0_interp);

  float input_f0 = 0;
  if (f0L.f0 > 0 && f0R.f0 > 0) //& f0_index
  {
    input_f0 = f0L.f0 * (1.0f - f0_interp) + f0R.f0 * f0_interp;
  }

  if (input_f0 == 0.0f)
    input_f0 = 500.0f;

  int posL = static_cast<int>(pmkL.pos * _samplerate);
  int posR = static_cast<int>(pmkR.pos * _samplerate);

  int period2 = static_cast<int>(_samplerate / input_f0 * 2 + 0.5f);

  if (_mbrPeriod) {
    // if using MBROLA algorithm
    int samples = localTime * _samplerate;
    int index = samples / _mbrPeriod;
    posL = index * _mbrPeriod;
    posR = posL + _mbrPeriod;
    pmk_interp = 0.0;
    period2 = _mbrPeriod;
  }

  *impulseResponseLength = period2;
  for (int i = 0; i < period2; i++) {
    float l = 0;
    float r = 0;
    float x = 0;
    if (i + posL < voice->input_data_length)
      l = voice->input_data[i + posL];
    if (i + posR < voice->input_data_length)
      r = voice->input_data[i + posR];
    x = r * (1 - pmk_interp) + l * pmk_interp;

    if (morph) {
      printf("morphing %f\n", morph);
      r = x;
      l = impulseResponse[i];
      x = r * morph + l * (1.0f - morph);
    }

    impulseResponse[i] = x;
  }
}

bool MyVoiceSampler::addOnePulse() {

  float currentTime = inputPositionSamples() / _samplerate;
  _phoEvents.selectNext(currentTime);
  _pitchEvents.selectNext(currentTime);

  PhoEvent *pho0 = _phoEvents.current();
  PhoEvent *pho1 = _phoEvents.next();

  PitchEvent *pit0 = _pitchEvents.current();
  float output_f0 = 50;
  if (pit0)
    output_f0 = frequencyFromNote(pit0->notenum);

  if (pho0 == nullptr)
    return false;

  if (currentTime >= pho0->start && currentTime < pho0->end) {
    float interp = 0;
    int impulseResponseLength = 0;

    getImpulseResponse(currentTime, pho0, _impulseResponse,
                       &impulseResponseLength,
                       0); // TODO get impulse response from mapped index

    if (pho1 && currentTime >= pho1->start) {
      interp = (currentTime - pho1->start) / (pho0->end - pho1->start);
    }

    if (interp > 0) {
      getImpulseResponse(currentTime, pho1, _impulseResponse,
                         &impulseResponseLength,
                         interp); // needs interp as input
    }

    int tmp = static_cast<int>(_samplerate * 1.0f / output_f0);
    float period = tmp;

    VoiceSampler::hanningWindow(_impulseResponse, impulseResponseLength);

    ola(_impulseResponse, impulseResponseLength, period);
    return true;
  }
  printf("end of file\n");
  // if(n0)
  return false;
}
int main(int argc, char **argv) {
  if (argc < 3) {
    printf("usage: eventlist_test /path/to/utauloid output.wav\n");
    return 1;
  }

  std::string voicepath = argv[1];
  std::string outfile = argv[2];
  MyVoiceSampler vosamp(8192, voicepath);

  SF_INFO info;
  ZERO_MEMORY(info);
  info.samplerate = vosamp.samplerate();
  info.channels = 1;
  info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;

  SNDFILE *sf = sf_open(outfile.c_str(), SFM_WRITE, &info);

  while (1) {
    const int size = 1024;
    int fill = size * 4;

    float buffer_out[size];
    if (vosamp.readData(buffer_out, size, fill) == false)
      break;
    sf_write_float(sf, buffer_out, size);
  }

  sf_close(sf);
}

// TODO: copyout, window function
